#include "./subckt.hh"
#include "./circuit.hh"
#include "./port.hh"

#include <cassert>
#include <sstream>
#include <string>
#include <vector>
#include <format>
#include <ranges>

namespace ycspice::preprocess {
    
    Subckt::Subckt(std::string name) :
        Circuit{std::move(name)} {}

    auto Subckt::expand_spice(std::string_view inst_name, std::span<std::string> inst_nets) const -> std::string {
        auto ss = std::stringstream{};
        this->do_expand_spice(inst_name, inst_nets, ss);
        return ss.str();
    }

    auto Subckt::do_expand_spice(std::string_view inst_name, std::span<std::string> inst_nets, std::stringstream& ss) const -> void {
        if (this->_ports.size() != inst_nets.size()) {
            // TODO error
            std::exit(1);
        }
        
        // Create a self circuit as `inst_name`, with `nets`
        for (auto& inst : this->_instances) {
            auto circuit = inst.circuit();
            auto new_inst_name = std::format("{}#{}", inst.name(), inst_name);
            auto new_inst_nets = inst.nets()
                | std::ranges::views::transform([&inst_nets, inst_name](Net* net) -> std::string {
                    if (net->name() == "0") {
                        return "0";
                    } else if (net->is_inner()) {
                        return inst_nets[net->connected_port()->index()];
                    } else {
                        return std::format("{}#{}", net->name(), inst_name);
                    }
                })
                | std::ranges::to<std::vector<std::string>>();
            
            ss << circuit->expand_spice(new_inst_name, new_inst_nets) << '\n';
        }

        for (auto& mutual_inductance : this->_mutual_inductances) {
            ss 
                << std::format("{}#{}", mutual_inductance.name(), inst_name) << ' '
                << std::format("{}#{}", mutual_inductance.inductance1(), inst_name) << ' '
                << std::format("{}#{}", mutual_inductance.inductance2(), inst_name) << ' '
                << mutual_inductance.value()
                << '\n';
        }
    }

    auto Subckt::add_instance(Circuit* circuit, std::string name, std::span<std::string> net_names) -> void {
        circuit->check_net_size(net_names);

        auto nets = std::vector<Net*>{};
        for (auto& net : net_names) {
            nets.emplace_back(this->get_net(std::move(net)));
        }

        this->_instances.emplace_back(circuit, std::move(name), nets);
    }

    auto Subckt::add_mutual_inductances(MutualInductance mutual_inductance) -> void {
        this->_mutual_inductances.emplace_back(std::move(mutual_inductance));
    }
    
    auto Subckt::get_net(std::string net_name) -> Net* {
        auto find_res = this->_nets.find(net_name);
        if (find_res != this->_nets.end()) {
            return &find_res->second;
        } else {
            // Is connect with port?
            auto op_port = std::find_if(this->_ports.begin(), this->_ports.end(), [&net_name](const Port& p) -> bool {
                return net_name == p.name();
            });
            Port* connected_port = nullptr;
            if (op_port != this->_ports.end()) {
                connected_port = &(*op_port);
            }

            auto net = Net{net_name, connected_port};
            auto res = this->_nets.emplace(std::move(net_name), std::move(net));
            assert(res.second == true);
            return &(res.first->second);
        }
    }
}